package jmine.tec.persist.hibernate.initializer;

import java.util.Collection;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import jmine.tec.utils.collection.IdentityHashSet;

import org.hibernate.EntityMode;
import org.hibernate.Hibernate;
import org.hibernate.SessionFactory;
import org.hibernate.metadata.ClassMetadata;
import org.hibernate.type.ComponentType;
import org.hibernate.type.Type;

/**
 * Inicializador recursivo, itera por todas as propriedades e delega a inicialização.
 * 
 * @author gabriel.silva
 * @author lundberg
 */
public class RecursiveInitializer implements ProxyInitializer {

    private SessionFactory sessionFactory;

    private ProxyInitializer delegate;

    /**
     * {@inheritDoc}
     */
    public void initialize(Object entity) {
        this.initializeRecursion(entity, new IdentityHashSet<Object>());
    }

    /**
     * Inicializa recursivamente uma entidade
     * 
     * @param entity entity
     * @param visited visited
     */
    private void initializeRecursion(Object entity, Set<Object> visited) {
        if (entity == null || visited.contains(entity)) {
            return;
        }
        visited.add(entity);
        this.delegate.initialize(entity);
        ClassMetadata classMetadata = this.sessionFactory.getClassMetadata(Hibernate.getClass(entity));
        if (classMetadata == null) {
            return;
        }
        Object resolvedObject = LazyResolverEnum.forObject(entity).resolve(entity);
        String[] properties = classMetadata.getPropertyNames();
        for (String property : properties) {
            Type type = classMetadata.getPropertyType(property);
            Object value = classMetadata.getPropertyValue(resolvedObject, property, EntityMode.POJO);

            if (type.isComponentType()) {
                ComponentType componentType = (ComponentType) type;
                Type[] componentPropertyTypes = componentType.getSubtypes();
                Object[] componentPropertyValues = componentType.getPropertyValues(value, EntityMode.POJO);
                for (int i = 0; i < componentPropertyTypes.length; i++) {
                    this.initializeValue(componentPropertyTypes[i], componentPropertyValues[i], visited);
                }
            } else {
                this.initializeValue(type, value, visited);
            }
        }
    }

    /**
     * Inicializa um valor de uma entidade ou embeddable
     * 
     * @param type type
     * @param value value
     * @param visited visited
     */
    private void initializeValue(Type type, Object value, Set<Object> visited) {
        if (value == null) {
            return;
        } else if (type.isCollectionType()) {
            this.delegate.initialize(value);
            if (value instanceof Map<?, ?>) {
                for (Entry entry : ((Map<?, ?>) value).entrySet()) {
                    this.initializeRecursion(entry.getKey(), visited);
                    this.initializeRecursion(entry.getValue(), visited);
                }
            } else {
                for (Object element : (Collection<?>) value) {
                    this.initializeRecursion(element, visited);
                }
            }
        } else if (type.isAssociationType()) {
            this.initializeRecursion(value, visited);
        }
    }

    /**
     * @param sessionFactory the sessionFactory to set
     */
    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    /**
     * @param delegate the delegate to set
     */
    public void setDelegate(ProxyInitializer delegate) {
        this.delegate = delegate;
    }
}
